package files

import (
	"fmt"
	"log/slog"
	"math"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

// 项目工作路径缓存
var workPath = ""

// GetWorkPath 取得项目工作路径（获取当前执行文件的绝对路径）
//   - 注意：结尾无DS路径分隔符
func GetWorkPath() string {
	if workPath != "" {
		return workPath
	}
	workPath = getWorkPath()
	return workPath
}
func getWorkPath() string {
	root := GetCurrentPath()
	if root == "" {
		return ""
	}
	// 兼容 Go Run&Test环境
	// 兼容 Go mod工作区
	if !IsFile(filepath.Join(root, "go.mod")) {
		rootMod := filepath.Dir(root)
		maxIterations, i := 100, 0 // 防止无限循环
		for ; i < maxIterations; i++ {
			nextRootMod := filepath.Dir(rootMod)
			if nextRootMod == rootMod {
				break // 避免无限循环
			}
			if IsFile(filepath.Join(rootMod, "go.mod")) {
				root = rootMod
				break
			} else {
				rootMod = nextRootMod
			}
		}
	}
	// 兼容 Go work 工作区
	if IsFile(filepath.Join(filepath.Dir(root), "go.work")) {
		root = filepath.Dir(root)
	}
	return filepath.Clean(root)
}

// GetCurrentPath 获取当前执行文件绝对路径
func GetCurrentPath() string {
	wPath := getCurrentPathByExecutable()
	if strings.Contains(wPath, getCurrentTmpPath()) {
		return getCurrentPathByCaller()
	}
	return wPath
}

// 获取当前执行文件绝对路径(兼容go build)
func getCurrentPathByExecutable() string {
	wPath, err := os.Getwd()
	if err != nil {
		wPath, err = os.Executable()
		if err != nil {
			wPath, err = filepath.Abs(filepath.Dir(os.Args[0]))
			if err != nil {
				slog.Error("getCurrentPathByExecutable", slog.String("error", err.Error()))
			}
		}
	}
	res, _ := filepath.EvalSymlinks(wPath)
	return res
}

// 获取当前执行文件绝对路径（兼容go run）
func getCurrentPathByCaller() string {
	_, filename, _, ok := runtime.Caller(0)
	if ok {
		wPath, err := filepath.EvalSymlinks(filepath.Dir(filename))
		if err != nil {
			slog.Error("getCurrentPathByCaller", slog.String("error", err.Error()))
		}
		return wPath
	}
	return ""
}

// 获取系统临时目录
func getCurrentTmpPath() string {
	wPath := os.Getenv("TEMP")
	if wPath == "" {
		wPath = os.Getenv("TMP")
	}
	if wPath == "" {
		wPath = os.TempDir()
	}
	res, err := filepath.EvalSymlinks(wPath)
	if err != nil {
		slog.Error("getCurrentTmpPath", slog.String("error", err.Error()))
	}
	return res
}

// FolderToPath 基于项目工作路径，把一个相对目录转换为绝对路径
func FolderToPath(folder string) (path_str string) {
	path_str = filepath.Clean(folder)
	if filepath.IsAbs(path_str) {
		return
	}
	var workPath = GetWorkPath()
	if strings.Index(path_str, workPath) != 0 {
		path_join, err := filepath.Abs(filepath.Join(workPath, path_str))
		if err != nil {
			slog.Error("FolderToPath", slog.String("error", err.Error()))
		}
		return path_join
	}
	return
}

// PathToFolder 基于项目工作路径，把一个绝对路径转换为相对目录
func PathToFolder(folder string) string {
	var workPath = GetWorkPath()
	folder, err := filepath.Rel(workPath, filepath.Clean(folder))
	if err != nil {
		slog.Error("PathToFolder", slog.String("error", err.Error()))
	}
	return folder
}

// PathExists 判断指定路径文件或目录是否存在
func PathExists(dirPath string) bool {
	_, err := os.Stat(filepath.Clean(dirPath))
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

// PathCreateIfNotExist 文件夹路径不存在则自动创建
//   - path string
//   - mode os.FileMode 如 os.FileMode(0777)  八进制的UNIX文件/文件夹权限模式
//   - -rw------- (0600)    只有拥有者有读写权限。
//   - -rw-r--r-- (0644)    只有拥有者有读写权限；而属组用户和其他用户只有读权限。
//   - -rwx------ (0700)    只有拥有者有读、写、执行权限。
//   - -rwxr-xr-x (0755)    拥有者有读、写、执行权限；而属组用户和其他用户只有读、执行权限。
//   - -rwx--x--x (0711)    拥有者有读、写、执行权限；而属组用户和其他用户只有执行权限。
//   - -rw-rw-rw- (0666)    所有用户都有文件读、写权限。
//   - -rwxrwxrwx (0777)    所有用户都有读、写、执行权限。
func PathCreateIfNotExist(dirPath string, mode os.FileMode) error {
	if is := PathExists(dirPath); !is {
		return PathCreate(dirPath, mode)
	}
	return nil
}

// IsDir 判断所给路径是否为一个文件夹
func IsDir(dirPath string) bool {
	s, err := os.Stat(filepath.Clean(dirPath))
	if err != nil {
		return false
	}
	return s.IsDir()
}

// IsFile 判断所给路径是否为一个文件
func IsFile(filePath string) bool {
	s, err := os.Stat(filepath.Clean(filePath))
	if err != nil {
		return false
	}
	return !s.IsDir()
}

// GetPathFiles 获取指定路径下的文件清单
// format 文件扩展名,如: json，如为*表示不限制
// isSub 是否包含子目录(递归)
func GetPathFiles(dirPath string, format string, isSub bool) (files []string, err error) {
	dirPath = filepath.Clean(dirPath)
	dir, err := os.ReadDir(dirPath)
	if err != nil {
		return []string{}, err
	}
	if format == "" {
		return []string{}, nil
	}
	if format != "*" && format[0] != '.' {
		format = "." + format
	}
	files = []string{}
	for _, fi := range dir {
		if !fi.IsDir() {
			// 过滤指定格式
			if format == "*" {
				files = append(files, filepath.Join(dirPath, fi.Name()))
			} else if format != "" && strings.HasSuffix(fi.Name(), format) {
				files = append(files, filepath.Join(dirPath, fi.Name()))
			}
		} else if isSub { // 目录, 递归遍历
			subFiles, _ := GetPathFiles(filepath.Join(dirPath, fi.Name()), format, isSub)
			if len(subFiles) > 0 {
				files = append(files, subFiles...)
			}
		}
	}
	return files, nil
}

// GetPathDirs 获取指定路径下的文件目录清单
// isSub 是否包含子目录(递归)
func GetPathDirs(path_str string, isSub bool) (paths []string, err error) {
	path_str = filepath.Clean(path_str)
	dirs, err := os.ReadDir(path_str)
	if err != nil {
		return []string{}, err
	}
	for _, fi := range dirs {
		if fi.IsDir() { // 目录, 递归遍历
			dir := filepath.Join(path_str, fi.Name())
			paths = append(paths, dir)
			if isSub {
				subDirs, _ := GetPathDirs(dir, isSub)
				if len(subDirs) > 0 {
					paths = append(paths, subDirs...)
				}
			}
		}
	}
	return paths, nil
}

// FormatByteSize 字节的单位转换, 最多保留两位小数（四舍五入）
func FormatByteSize(fileSize int64) (size string) {
	if fileSize == 0 {
		return "0B"
	}
	// 定义单位和对应的字节数
	units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB"}
	base := float64(1024)

	// 计算并选择合适的单位
	var unitIndex int
	value := float64(fileSize)
	isNegative := value < 0
	if isNegative {
		value = -value
	}
	for value >= base && unitIndex < len(units)-1 {
		value /= base
		unitIndex++
	}
	x := math.Pow10(2)
	value = math.Round(value*x) / x
	if isNegative {
		return fmt.Sprint("-", value, units[unitIndex])
	} else {
		return fmt.Sprint(value, units[unitIndex])
	}
}
